import static org.junit.Assert.*;

import org.junit.*;
/**
 * VendingMachineTest is used to test VendingMachineTest
 * using JUnit 4.0 specifically:
 * 1- addItem() method.
 * 2- VendingMachineItem removeItem method.
 * 3- insertMoney method.
 * 4- getBalance method.
 * 5- makePurchase method.
 * 6- returnChange method.
 * 
 * @author Mohammed Alghareeb
 *  
 */
public class VendingMachineTest {
	
	static VendingMachineItem item = new VendingMachineItem ("Kitkat", 1.25);
	static VendingMachineItem item1 = new VendingMachineItem ("Pepsi", 1.25);
	static VendingMachineItem item2 = new VendingMachineItem ("Milk", 1.25);

	static VendingMachineItem badItem = new VendingMachineItem ("--", 2.25);
	static VendingMachine vendMachineOne = new VendingMachine ();
	
	/**
	 * setUpClass() runs once at the beginning of the test.
	 */
	@BeforeClass public static void setUpClass()
	{
		System.out.println("\t\t *Commencing VendingMachine unite testing*");
		printChar('=',70);

	}
	
	/**
	 * setUp() runs before each unit test.
	 */
	@Before public void setUp()
	{
		System.out.println("= Preparing Next test");
		printChar('=',70);
		vendMachineOne = new VendingMachine(); // reseting the vending machine
		item = new VendingMachineItem ("Kitkat", 1.25);
		vendMachineOne.addItem(item, "A");
		
	}
	
	/**
	 * testAddItem() is used to test:
	 * 1- Add item with correct params (VendingMachineItem, CorrectCode)
	 * 2- Adding item with incorrect params and observing the behavior  
	 */
	@Test public void testAddItem()
	{
		System.out.println("Step 1 - testAddItem();");
		System.out.println("testAddItem() started ");
		// Tests add Item with correct parameter (VendingMachineItem, CorrectCode)
		addingItemWithLegalParams(); 
		// Tests with wrong item code that should throw exception
		addingItemWithIllegalParams();	
		//Test if the item code is not empty
		addToFullSlot();		
		System.out.println("testAddItem() ended successfully");
	}

	/**
	 * testRemoveItem() is used to test and observe:
	 * 1- Removing existing items.
	 * 2- Removing non-existing items.
	 */
	@Test public void testRemoveItem() 
	{
		System.out.println("Step 2 - testRemoveItem();");
		System.out.println("testRemoveItem() started ");
		//Item existing item
		removingExistingItem();
		//Item non-existing item 
		removingNonExistingItem();
		//Test remove an empty item
		removeEmptyItem();
		
		System.out.println("testRemoveItem() ended successfully");
		
	}

	/**
	 * testInsertMoney() is used to test and observe:
	 * 1- Adding a negative balance.
	 * 2- Adding a positive  correct balance.
	 */
	@Test public void testInsertMoney()
	{
		System.out.println("Step 3 - testInsertMoney();");
		System.out.println("testInsertMoney() started ");
		//test balance if it is < 0
		insertingNegativeAmount();
		// test balance if it is  > 0
		insertingPositiveAmount();
		System.out.println("testInsertMoney() ended successfully");

	}

	/**
	 * testGetBalance() is used to test and observe
	 * the ability to retrieve the balance using getBalanace()
	 */
	@Test public void testGetBalance()
	{
		System.out.println("Step 4 - testing testGetBalance();");
		System.out.println("testGetBalance() started ");
		//test the ability to retrieve the balance using getBalanace()
		double expectedBalance = 0.0;
		double actualBlanace = vendMachineOne.getBalance();		
		assertEquals(expectedBalance, actualBlanace, 0.0001);
		System.out.println("testGetBalance() ended successfully");
	}
	
	/**
	 * testMakePurchase() is used to test and observe:
	 * 1- Buying non-existing item.
	 * 2- Buying with low Balance.
	 * 3- Buying an existing item with correct balance.
	 */
	@Test public void testMakePurchase()
	{
		System.out.println("Step 5 - testing makePurchase();");
		System.out.println("testMakePurchase() started ");
		// test if the item does not exists	
		purchasingNonExistingItem();		
		// test if the balance is < price and item exists.
		buyBalanceIsNotEnough();		
		// test if price < balance and item exists
		buyingItemCorrectly();		
		System.out.println("testMakePurchase() ended successfully");
	}

	/**
	 * testReturnChange() is used to test and observe
	 * the ability to return the change  using testReturnChange()
	 * and then zeroing the balance amount.
	 */
	@Test public void testReturnChange()
	{
		System.out.println("Step 6 - testing returnChange();");
		System.out.println("testReturnChange() started ");
		// Test return change after adding money to change
		double  addedBalance = 1.25;
		vendMachineOne.insertMoney(addedBalance);
		double expectedChange = vendMachineOne.returnChange();
		assertEquals(expectedChange , addedBalance, 0.0001);
		//Test the new Balance that should be Zero
		double expectedNewBalance = 0.0, newBal = vendMachineOne.getBalance();		
		assertEquals(expectedNewBalance , newBal, 0.0001);
		System.out.println("testReturnChange() ended successfully");
	}


	/**
	 * After() runs after each test. 
	 */
	@After public void tearDown()
	{		
		System.out.println("@Test completed and passed successfully");
		printChar('=',70);
	}

	/**
	 * AfterClass() runs once at the end of all the tests.
	 */
	@AfterClass public static void tearDownClass()
	{
		System.out.println("\t\t *VendingMachine tests passed successfully*");
		printChar('=',70);
	}

	private void removeEmptyItem() {
		Throwable caught = null;
		
		try{
			vendMachineOne.removeItem("B");			
		}catch(VendingMachineException exp){
			caught = exp;
			System.out.println(exp.getMessage());
		}
		assertNotNull(caught);
		assertSame(VendingMachineException.class, caught.getClass());
	}

	private void addToFullSlot() {
		Throwable caught = null;
		
		try{
			vendMachineOne.addItem(badItem, "B");			
		}catch(VendingMachineException exp){
			caught = exp;
			System.out.println(exp.getMessage());
		}
		assertNotNull(caught);
		assertSame(VendingMachineException.class, caught.getClass());
	}

	private void buyingItemCorrectly() {
		boolean expectedCode;
		vendMachineOne.insertMoney(1.25);
		expectedCode = vendMachineOne.makePurchase("A");
		assertEquals(expectedCode, true);//this should return true and pass
	}

	private void buyBalanceIsNotEnough() {
	 vendMachineOne.returnChange(); //makes sure the balance is @ Zero
		boolean expectedCode = vendMachineOne.makePurchase("A");
		assertEquals(expectedCode, false); //this should return false & not pass
	}

	private void purchasingNonExistingItem() {
		boolean expectedCode = false;
		try
		{
			expectedCode = vendMachineOne.makePurchase("N/A");
		}catch(VendingMachineException exp){
			System.out.println(exp.getMessage());
		}
		assertEquals(expectedCode, false);//this should return false & not pass
	}
	
	/**
	 *  This method adds a positive amount to the balance 
	 *  
	 */
	private void insertingPositiveAmount() {
		double balance = vendMachineOne.getBalance();
		double newBalance = balance + 1.25;
				
		//try
		//{
			vendMachineOne.insertMoney(1.25);
			balance = vendMachineOne.getBalance();
			assertEquals(balance, newBalance, 0.0001);
			
		//}catch( VendingMachineException exp )
		//{
		//	System.out.println(exp.getMessage());
	//	}
		
		System.out.println("Balance after adding  1.25 is = "+ vendMachineOne.getBalance());
	}

	/**
	 * This method checks if the system will take in 
	 * a negative amount and assigns it to the balance
	 */
	private void insertingNegativeAmount() throws VendingMachineException {
		try
		{
			vendMachineOne.insertMoney(-1.25);
		}catch( VendingMachineException exp )
		{
			System.out.println("Adding (- 1.25)  is "+ exp.getMessage());
			//System.out.println("The balance is still at ");
		}
	}
	
	/**
	 * This method tried to remove items from the vendingMachine 
	 */
	private void removingExistingItem() {
		
			vendMachineOne.removeItem("A");
			assertNull(vendMachineOne.getItem("A"));
	}

	/**
	 * This method observes the behavior of removing non-existing item
	 */
	private void removingNonExistingItem() {
		try
		{	
			vendMachineOne.removeItem("G");
		}catch(VendingMachineException e)
		{
			System.out.println(e.getMessage());
		}
	}
	
	/**
	 * This method checks and observes the ability
	 * to add an item with an out of bound slot index
	 * to the vending machine
	 */
	private void addingItemWithIllegalParams () throws VendingMachineException 
	{
		 try{
			 	vendMachineOne.addItem(badItem, "E");			 		 	
				System.out.println("Adding an item with an out of bound index/code did not succeed");

		 }catch (VendingMachineException exp){
			 System.out.println(exp.getMessage());
		 }
		
	}

	/**
	 * This method checks and observes the ability
	 * to add an item to the vending machine
	 */
	private void addingItemWithLegalParams() {
		vendMachineOne.addItem(item, "B");
		String expectedItemName = vendMachineOne.getItem("B").getName();
		assertEquals(expectedItemName,"Kitkat");
		vendMachineOne.addItem(item1, "C");
		expectedItemName = vendMachineOne.getItem("C").getName();
		assertEquals(expectedItemName,"Pepsi");
		vendMachineOne.addItem(item2, "D");
		expectedItemName = vendMachineOne.getItem("D").getName();
		assertEquals(expectedItemName,"Milk");
		
		
	
	}
	
	/**
	 * This method is used to print multiple characters when called.
	 * @param character
	 * @param times
	 */
	public static void printChar(char character, int times) {
		int i;
		for (i = 0 ; i <= times; i++)
		{
			System.out.print(character);
		}
		System.out.println();
	
	}


}
